package cn.sciento.starter.tenant.custom.core;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import org.apache.commons.lang3.ArrayUtils;
import cn.sciento.starter.tenant.custom.annotation.TenantCustom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.stereotype.Controller;

public class TenantCustomPostProcessor implements BeanDefinitionRegistryPostProcessor {
  private static final Logger LOGGER = LoggerFactory.getLogger(TenantCustomPostProcessor.class);
  
  private static Set<Class<?>> dynamicInterfaceCache = new HashSet<>(64);
  
  public void postProcessBeanDefinitionRegistry(@Nonnull BeanDefinitionRegistry registry) throws BeansException {}
  
  public void postProcessBeanFactory(@Nonnull ConfigurableListableBeanFactory beanFactory) throws BeansException {
    String[] customBeanNames = beanFactory.getBeanNamesForAnnotation(TenantCustom.class);
    if (ArrayUtils.isEmpty(customBeanNames))
      return; 
    for (String beanName : customBeanNames) {
      if (beanFactory.findAnnotationOnBean(beanName, Controller.class) == null) {
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
        String className = beanDefinition.getBeanClassName();
        try {
          Class<?> clazz = Class.forName(className);
          do {
            Class<?>[] interfaces = clazz.getInterfaces();
            clazz = clazz.getSuperclass();
            if (ArrayUtils.isEmpty((Object[])interfaces))
              continue; 
            for (Class<?> interfaceClazz : interfaces)
              processDynamicCustomBean(interfaceClazz, beanFactory); 
          } while (clazz != null);
        } catch (Exception e) {
          LOGGER.warn("dynamicService set primary is wrong!", e);
        } 
      } 
    } 
  }
  
  protected void processDynamicCustomBean(Class<?> interfaceClazz, ConfigurableListableBeanFactory beanFactory) {
    if (dynamicInterfaceCache.contains(interfaceClazz))
      return; 
    LOGGER.info("customBeanDefinitionProcessor set primary: {}", interfaceClazz.toString());
    String[] dynamicService = beanFactory.getBeanNamesForType(interfaceClazz);
    if (ArrayUtils.isEmpty(dynamicService) || dynamicService.length == 1)
      return; 
    List<String> dynamicList = new ArrayList<>();
    int noTenantServiceCount = 0;
    for (String name : dynamicService) {
      if (!name.startsWith("scopedTarget.")) {
        if (beanFactory.findAnnotationOnBean(name, TenantCustom.class) == null)
          noTenantServiceCount++; 
        dynamicList.add(name);
      } 
    } 
    if (dynamicList.size() > 1 && noTenantServiceCount == 1) {
      GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
      beanDefinition.setBeanClass(DynamicTenantCustomFactoryBean.class);
      beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, interfaceClazz);
      beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, dynamicService);
      beanDefinition.setAutowireMode(2);
      beanDefinition.setPrimary(true);
      DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory)beanFactory;
      defaultListableBeanFactory.registerBeanDefinition(interfaceClazz.getSimpleName(), (BeanDefinition)beanDefinition);
    } 
    dynamicInterfaceCache.add(interfaceClazz);
  }
}
